# -*- coding: utf-8 -*-

import re
import datetime

from django.db import models
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned

# Create your models here.

from mhd.exceptions import *



class MHD_Link_Type(models.Model):    
    name = models.CharField(max_length=255)
    color = models.CharField(max_length=255)
    icon = models.CharField(max_length=255)

    def __unicode__(self) :
        return self.name


class MHD_Stop_Type(models.Model):    
    name = models.CharField(max_length=255)

    def __unicode__(self) :
        return self.name
    

class MHD_Link(models.Model):    
    name = models.CharField(max_length=255)
    link_type = models.ForeignKey(MHD_Link_Type)
    stops_ = None
    directions_ = None
    
    
    def getNextArrival(self, stopId, directionId, timetable_label, desired):
        nextArrival = None
        waiting = None
        try:
            mhd_link_stop = MHD_Link_Stop.objects.get(link = self, stop__id = stopId, direction__id = directionId)
            mhd_link_stop_base = MHD_Link_Stop.objects.get(link = self, stop_number = 1, direction = mhd_link_stop.direction)
        except ObjectDoesNotExist:
            raise NoSuchLinkStop         
        except MultipleObjectsReturned:
            pass   
        arrivals_base = timetable_models[timetable_label].objects.filter(link_stop = mhd_link_stop_base, arrival__gte = desired.time())
        if arrivals_base:
            arrival_base = arrivals_base[0].arrival
            try:
                delta = MHD_Link_Timetable_Delta.objects.get(link_stop_base = mhd_link_stop_base, stop__id = stopId)
            except ObjectDoesNotExist:
                raise NoSuchTimetableDelta
            nextArrival = datetime.datetime(desired.year, desired.month, desired.day, arrival_base.hour, arrival_base.minute) + datetime.timedelta(seconds = delta.delta)
            waiting = int((nextArrival - desired).seconds / 60)
            nextArrival = nextArrival.time()
        else:
            raise InvalidArrival
        return (nextArrival, waiting)
    
    
    def stopsAt(self, other):
        rv = False
        if not self.stops_:
            self.init_stops__()
        for stop in self.stops_:
            if stop == other:
                rv = True
                break           
        return rv
    
    def getDirections(self):
        if not self.stops_:
            self.init_stops__()
        return self.directions_
    
    # TODO: raise exception if direction does not exist
    def getLastStop(self, direction):
        if not self.stops_:
            self.init_stops__()
        return self.stops_[direction.id][len(self.stops_[direction]) - 1]
    
    
    def getNumStops(self, direction): 
        if not self.stops_:
            self.init_stops__()
        return len(self.stops_[direction.id])


    # TODO: raise exception if direction does not exist or num_stop exceeds num_stops
    def getStop(self, num_stop, direction):
        if not self.stops_:
            self.init_stops__()
        if num_stop > len(self.stops_[direction.id]):
            raise NumStopOutOfRange
        return self.stops_[direction.id][num_stop - 1]

    
    def operatesWorkDays(self):
        rv = False
        link_stops = MHD_Link_Stop.objects.filter(link = self, stop_number = 1)        
        if link_stops:
            rv = MHD_Link_Timetable_Wrkday_School.objects.filter(link_stop = link_stops[0]).count() > 0
        return rv        
    
    
    def operatesSchoolHolidays(self):
        rv = False
        link_stops = MHD_Link_Stop.objects.filter(link = self, stop_number = 1)        
        if link_stops:
            rv = MHD_Link_Timetable_Wrkday_Noschool.objects.filter(link_stop = link_stops[0]).count() > 0
        return rv        

        
    def operatesWeekends(self):
        rv = False
        link_stops = MHD_Link_Stop.objects.filter(link = self, stop_number = 1)        
        if link_stops:
            rv = MHD_Link_Timetable_Weekend.objects.filter(link_stop = link_stops[0]).count() > 0
        return rv
    
    
    def getDelta(self, linkStopBase, prev_stop, this_stop):
        try:
            prev_timetable_delta = MHD_Link_Timetable_Delta.objects.get(link_stop_base = linkStopBase, stop = prev_stop)
            this_timetable_delta = MHD_Link_Timetable_Delta.objects.get(link_stop_base = linkStopBase, stop = this_stop)
            # HACK: it should not happen that this delta is less than prev delta, yet it does..hence use abs()
            return abs(this_timetable_delta.delta - prev_timetable_delta.delta)
        except ObjectDoesNotExist:
            raise NoSuchStop
            
        
    def __unicode__(self) :
        return self.name
    
    def __eq__(self, other):        
        return self.name == other.name
        
    def init_stops__(self):
        self.stops_ = {}
        self.directions_ = {}
        for direction in MHD_Link_Direction.objects.filter(link = self):
            self.directions_[direction.id] = direction
            stopList = []
            for linkStop in MHD_Link_Stop.objects.filter(link = self, direction = direction).order_by('stop_number'):
                stopList.append(linkStop.stop)
            self.stops_[direction.id] = stopList
            
            

class MHD_Stop(models.Model):
    name = models.CharField(max_length=255)
    zoid = models.IntegerField(default = 0)
    lat = models.FloatField()
    lng = models.FloatField()
    stop_type = models.ForeignKey(MHD_Stop_Type)
    hint = models.CharField(max_length=255)
    zone = models.IntegerField(default = 0)
    links_ = None    
    
    def getLatLng(self):
        return '%s,%s' % (self.lat, self.lng)
    
    
    def getLinks(self):
        if not self.links_:
            self.init_links__()
        return self.links_        
    
    
    def __unicode__(self) :
        return self.name
    
    
    def __eq__(self, other):
        return self.lat == other.lat and self.lng == other.lng        
    
    
    def init_links__(self):
        self.links_ = []
        for linkStop in MHD_Link_Stop.objects.filter(stop = self):
            self.links_.append(linkStop.link)
            
            
class MHD_Link_Direction(models.Model):
    link = models.ForeignKey(MHD_Link)
    direction = models.CharField(max_length=255)
    timetable_url = models.CharField(max_length=255)

    def __unicode__(self) :
        return self.direction

    def getTimetableUrlParams(self):
        timetableUrlParams = {}
        m = re.match('.*?w=(\w+)&l=(\w+)&x=(\w+)', self.timetable_url)
        if m:
            timetableUrlParams['w'] = m.group(1)
            timetableUrlParams['l'] = m.group(2)
            timetableUrlParams['x'] = m.group(3)
        return timetableUrlParams
        
        
    


class MHD_Link_Stop(models.Model):    
    link = models.ForeignKey(MHD_Link)
    stop = models.ForeignKey(MHD_Stop)
    stop_number = models.IntegerField(default = 0)
    direction = models.ForeignKey(MHD_Link_Direction) 

    def __unicode__(self) :
        return "link: %s, stop: %s, stop_number: %d, direction: %s" % (self.link, self.stop, self.stop_number, self.direction)
    
    
class MHD_Stop_Transfer(models.Model):
    from_stop = models.ForeignKey(MHD_Stop, related_name='+')
    to_stop = models.ForeignKey(MHD_Stop, related_name='+')
    distance = models.IntegerField(default = 0)
    duration = models.CharField(max_length=10, default = '')
    
    
class MHD_Stop_Navigation(models.Model):
    stop_transfer = models.ForeignKey(MHD_Stop_Transfer)
    distance = models.CharField(max_length = 256)
    duration = models.CharField(max_length = 256)
    instructions = models.CharField(max_length = 1024)
    
    
class MHD_Link_Timetable_Wrkday_School(models.Model):
    link_stop = models.ForeignKey(MHD_Link_Stop)
    arrival = models.TimeField()
    

class MHD_Link_Timetable_Wrkday_Noschool(models.Model):
    link_stop = models.ForeignKey(MHD_Link_Stop)
    arrival = models.TimeField()
    
    
class MHD_Link_Timetable_Weekend(models.Model):    
    link_stop = models.ForeignKey(MHD_Link_Stop)
    arrival = models.TimeField()


class MHD_Link_Timetable_Delta(models.Model):
    link_stop_base = models.ForeignKey(MHD_Link_Stop)
    stop = models.ForeignKey(MHD_Stop)
    delta = models.IntegerField(default = 0)


class MHD_Holiday(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()
    valid_for = models.CharField(max_length = 256, null=True, blank=True)
    class Meta:
        abstract = True    
    
class MHD_School_Holiday(MHD_Holiday):
    pass

class MHD_Public_Holiday(MHD_Holiday):
    pass

timetable_models = {'wrk_days_school': MHD_Link_Timetable_Wrkday_School, 'wrk_days_noschool': MHD_Link_Timetable_Wrkday_Noschool, 'weekend': MHD_Link_Timetable_Weekend}